#include "storage.h"
#include <QList>

#include "utils.h"

typedef QMap<QString, QVector<Line>> NamedDict;

struct FileIndex {
    NamedDict m_dicLevel;       //
    NamedDict m_dicTime;          // key is secends
    NamedDict m_dicThreadId;
    NamedDict m_dicCate1;
    NamedDict m_dicCate2;
    NamedDict m_dicCate3;
    NamedDict m_dicKeyword;
    long int m_lineCount;
    typedef QStringRef RecordType[7];

    bool RegRecord(const RecordType & items, const QStringRef &, qint64 posStart, qint64 posEnd){

        struct FillDict{
            static void doFillDict(NamedDict & dict, const QString & key, qint64 posStart, qint64 posEnd){
                auto itorFind = dict.find(key);
                if (itorFind == dict.end()){
                    itorFind = dict.insert(key, QVector<Line>());
                    itorFind.value().push_back(Line(posStart, posEnd - posStart));
                }else{
                    // Range & lastRange = itorFind.value().last();
                    // if (lastRange.end == posStart){
                    //    lastRange.end = posEnd;
                    // }else{
                        itorFind.value().push_back(Line(posStart, posEnd - posStart));
                    // }
                }
            }
        };

        static QString key;

        // level
        key.clear(); items[0].appendTo(&key);
        FillDict::doFillDict(m_dicLevel, key, posStart, posEnd);

        // time
        if (items[1].length() < 19) { return false; }
        key.clear(); items[1].left(19).appendTo(&key);
        FillDict::doFillDict(m_dicTime, key, posStart, posEnd);

        // threadId
        key.clear(); items[3].appendTo(&key);
        FillDict::doFillDict(m_dicThreadId, key, posStart, posEnd);

        // cate1
        key.clear(); items[4].appendTo(&key);
        FillDict::doFillDict(m_dicCate1, key, posStart, posEnd);

        // cate2
        key.clear(); items[5].appendTo(&key);
        FillDict::doFillDict(m_dicCate2, key, posStart, posEnd);

        // cate3
        key.clear(); items[6].appendTo(&key);
        FillDict::doFillDict(m_dicCate3, key, posStart, posEnd);

        ++m_lineCount;
        return true;
    }

    static bool searchDict(const QString & key, const NamedDict & dict, QSet<Line> & result){
        if (key.isNull() || key.isEmpty()){ return true; }
        auto itorFind = dict.find(key);
        if (itorFind != dict.end() && itorFind.value().size()){
            if (result.empty()){
                result = itorFind.value().toList().toSet();
                return true;
            }else{
                Utils::cross(itorFind.value(), result);
                return !result.empty();
            }
        }else {
            result.clear();
            return false;
        }
    }

    void Search(const QString & level, const QString & time, const QString & threadId, const QString & cate1, const QString & cate2, const QString & cate3, QSet<Line> & result){
        result.clear();
        if (!searchDict(level, m_dicLevel, result)){ return; }
        if (!searchDict(time, m_dicTime, result)){ return; }
        if (!searchDict(threadId, m_dicThreadId, result)){ return; }
        if (!searchDict(cate1, m_dicCate1, result)){ return; }
        if (!searchDict(cate2, m_dicCate2, result)){ return; }
        if (!searchDict(cate3, m_dicCate3, result)){ return; }
    }

    FileIndex() : m_lineCount(0) {}
};

Storage::Storage()
    // : m_lineCount(0)
{

}

Storage::~Storage(){
    for (auto pair : m_dictFileIndex) { delete pair; }
}

FileIndex * Storage::GetFile(const QString & name) const {
    auto itorFind = m_dictFileIndex.find(name);
    return itorFind != m_dictFileIndex.end() ? itorFind.value() : nullptr;
}

class FileIndex * Storage::AddFile(const QString & name) {
    auto itorFind = m_dictFileIndex.find(name);
    if (itorFind != m_dictFileIndex.end()){ return nullptr; }
    auto fileIndex = new FileIndex;
    m_dictFileIndex.insert(name, fileIndex);
    return fileIndex;
}

void Storage::RemoveFile(const QString & name) {
    auto itorFind = m_dictFileIndex.find(name);
    if (itorFind == m_dictFileIndex.end()) { return; }
    delete itorFind.value();
    m_dictFileIndex.erase(itorFind);
}

template<class ResultList, class StringSource>
static void splitString(const StringSource &source, const QString & sep,
                              QString::SplitBehavior behavior, Qt::CaseSensitivity cs, int maxCount, ResultList & list)
{
    int start = 0;
    int end;
    int extra = 0;

    // while ((end = Qt::qFindString(source.constData(), source.size(), start + extra, sep, separatorSize, cs)) != -1) {
    --maxCount;
    while (list.size() < maxCount && (end = source.indexOf(sep, start + extra, cs)) != -1) {
        if (start != end || behavior == QString::KeepEmptyParts)
            list.append(source.mid(start, end - start));
        start = end + sep.size();
        extra = (sep.size() == 0 ? 1 : 0);
    }
    if (start != source.size() || behavior == QString::KeepEmptyParts)
        list.append(source.mid(start, -1));
}



bool Storage::ParseLine(QStringRef items[ITEM_COUNT], QStringRef & desc, const QString & text) {

    static const QChar START = '[';
    static const QString SEP1 = "] [";
    static const QString SEP2 = "]: ";
    if (text.isEmpty() || text.isNull() || text[0] != START){ return false; }

    // auto vec = text.splitRef(SEP1);
    static QVector<QStringRef> vec; vec.clear();
    splitString<QVector<QStringRef> >(QStringRef(&text), SEP1, QString::KeepEmptyParts, Qt::CaseSensitive, ITEM_COUNT, vec);

    if (vec.size() != ITEM_COUNT) { return false; }
    items[0] = vec[0].mid(1);
    items[1] = vec[1];
    items[2] = vec[2];
    items[3] = vec[3];
    items[4] = vec[4];
    items[5] = vec[5];
    auto vec2 = vec[6].split(SEP2);
    if (vec2.size() != 2) { return false; }
    items[6] = vec2[0];
    desc = vec2[1];
    return true;

//    const auto START = '[';
//    const auto END = ']';
//    const auto SEP = ' ';
//    const auto DESC = ':';
//    int itemIdx = 0, i = 0;
//    if (text.isEmpty() || text.isNull() || text[0] != START){ return false; }
//    for(; i<text.size() && itemIdx<ITEM_COUNT; ++i){
//        auto c = text[i];
//        if (c == START){
//            items[itemIdx].clear();
//        }else if (c == END){
//            ++i; ++itemIdx;
//            if (text[i] == SEP) { continue; }
//            if (text[i] == DESC) {
//                if (text[++i] != SEP) { return false; }
//                ++i; break;
//            }
//            return false;
//        }else{
//            items[itemIdx].push_back(c);
//        }
//    }
//    if (itemIdx != ITEM_COUNT){ return false; }
//    desc.clear();
//    desc.append(text.begin()+i, text.length() - i);
//    return true;
}

long int Storage::GetLineCount() const {
    long int result = 0;
    for (auto & item : m_dictFileIndex) {
        result += item->m_lineCount;
    }
    return result;
}

void Storage::RegLine(FileIndex * fileIndex, const QString & text, qint64 posStart, qint64 posEnd) {
    Q_ASSERT(posStart < posEnd);
    static QStringRef items[ITEM_COUNT], desc;
    if (ParseLine(items, desc, text)) {
        // add to FileIndex
        if (fileIndex->RegRecord(items, desc, posStart, posEnd)){
            // ++m_lineCount;
        }
    }else{
        // ++m_invalidLineCount;
    }
}

//void Storage::RegLine(FileIndex * fileIndex, const std::string & text) {
//    const auto START = '[';
//    const auto END = ']';
//    const auto SEP = ' ';
//    const auto DESC = ':';
//    const auto ITEM_COUNT = 7;

//    static std::string items[ITEM_COUNT], desc;
//    unsigned int itemIdx = 0, i = 0;
//    if (text.empty() || text[0] != START){ goto L_FAILED; }
//    for(; i<text.size() && itemIdx<ITEM_COUNT; ++i){
//        auto c = text[i];
//        if (c == START){
//            items[itemIdx].clear();
//        }else if (c == END){
//            ++i; ++itemIdx;
//            if (text[i] == SEP) { continue; }
//            if (text[i] == DESC) {
//                if (text[++i] != SEP) { goto L_FAILED; }
//                ++i; break;
//            }
//            goto L_FAILED;
//        }else{
//            items[itemIdx].push_back(c);
//        }
//    }
//    if (itemIdx != ITEM_COUNT) { goto L_FAILED; }
//    desc.assign(text.begin()+i, text.end());


//    // add to FileIndex
//    // fileIndex->dicLevel.


//    ++m_lineCount;
//    return;

//L_FAILED:
//    ++m_invalidLineCount;
//}

static void mergeSet(QSet<QString> & set, const NamedDict & dict){
    for(auto itor=dict.cbegin(); itor!=dict.cend(); ++itor){
        set.insert(itor.key());
    }
}

void Storage::Rebuild(){
    m_levels.clear();
    m_times.clear();
    m_threadIds.clear();
    m_cate1s.clear();
    m_cate2s.clear();
    m_cate3s.clear();
    for(auto fileIndex : m_dictFileIndex){
        mergeSet(m_levels, fileIndex->m_dicLevel);
        mergeSet(m_times, fileIndex->m_dicTime);
        mergeSet(m_threadIds, fileIndex->m_dicThreadId);
        mergeSet(m_cate1s, fileIndex->m_dicCate1);
        mergeSet(m_cate2s, fileIndex->m_dicCate2);
        mergeSet(m_cate3s, fileIndex->m_dicCate3);
    }
}

void Storage::Search(const QString & filename, const QString & level, const QString & time, const QString & threadId, const QString & cate1, const QString & cate2, const QString & cate3, QSet<Line> & result){
    result.clear();
    auto itorFind = m_dictFileIndex.find(filename);
    if (itorFind == m_dictFileIndex.end()){ return; }
    itorFind.value()->Search(level, time, threadId, cate1, cate2, cate3, result);
}


//    int itemIdx = 0, i = 0;
//    if (text.isEmpty() || text.isNull() || text[0] != START){ goto L_FAILED; }
//    for(; i<text.size() && itemIdx<ITEM_COUNT; ++i){
//        auto c = text[i];
//        if (c == START){
//            items[itemIdx].clear();
//        }else if (c == END){
//            ++i; ++itemIdx;
//            if (text[i] == SEP) { continue; }
//            if (text[i] == DESC) {
//                if (text[++i] != SEP) { goto L_FAILED; }
//                ++i; break;
//            }
//            goto L_FAILED;
//        }else{
//            items[itemIdx].push_back(c);
//        }
//    }
//    if (itemIdx != ITEM_COUNT) { goto L_FAILED; }
